#ifndef HASIO_DETAIL_SOCKET_OPS_HPP
#define HASIO_DETAIL_SOCKET_OPS_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200

#include "../error.hpp"
#include "socket_types.hpp"

namespace hasio {
namespace detail {
namespace socket_ops {

using io_buf = iovec;

inline void get_last_error(hasio::error_code &ec, bool is_error_condition)
{
    if (!is_error_condition)
    {
        ec.assign(0, ec.category());
        return;
    }

    ec.assign(errno, std::system_category());  // since c++11
}

template <typename SockAddrLenType>
inline int do_bind(SockAddrLenType msghdr::*,
    socket_type fd, const socket_addr_type* addr, size_t addrlen)
{
    return ::bind(fd, addr, static_cast<SockAddrLenType>(addrlen));
}

int bind(socket_type fd, const socket_addr_type* addr, size_t addrlen, 
    hasio::error_code &ec)
{
    if (fd == invalid_socket)
    {
        ec = hasio::error::bad_descriptor;
        return socket_error_retval;
    }

    int result = do_bind(&msghdr::msg_namelen, fd, addr, addrlen);
    get_last_error(ec, result != 0);
    return result;
}

int listen(socket_type fd, int backlog, hasio::error_code &ec)
{
    int result = ::listen(fd, backlog);
    get_last_error(ec, result != 0);
    return result;
}

int close(socket_type fd, hasio::error_code &ec)
{
    int result = ::close(fd);
    get_last_error(ec, result != 0);
    return result;
}

int shutdown(socket_type fd, int how, hasio::error_code &ec)
{
    if (fd == invalid_socket)
    {
        ec = hasio::error::bad_descriptor;
        return socket_error_retval;
    }

    int result = ::shutdown(fd, how);
    get_last_error(ec, result != 0);
    return result;
}

bool set_non_blocking(socket_type fd, bool on, hasio::error_code &ec)
{
    ioctl_arg_type arg = (on ? 1 : 0);
    int result = ::ioctl(fd, FIONBIO, &arg);
    get_last_error(ec, result < 0);

    return result;
}

template<typename SockAddrLenType>
inline socket_type do_accept(SockAddrLenType msghdr::*,
    socket_type fd, socket_addr_type* addr, size_t* addrlen)
{
    SockAddrLenType tmp_addrlen = addrlen ? static_cast<SockAddrLenType>(*addrlen) : 0;
    socket_type new_fd = ::accept(fd, addr, addrlen ? &tmp_addrlen : nullptr);
    if (addrlen)
    {
        *addrlen = static_cast<size_t>(tmp_addrlen);
    }
    return new_fd;
}

socket_type accept(socket_type fd, socket_addr_type* addr, size_t* addrlen, 
    hasio::error_code &ec)
{
    if (fd == invalid_socket)
    {
        ec = hasio::error::bad_descriptor;
        return invalid_socket;
    }
    socket_type new_fd = do_accept(&msghdr::msg_namelen, fd, addr, addrlen);
    get_last_error(ec, new_fd == invalid_socket);
    return new_fd;
}

signed_size_type send(socket_type fd, size_t count, const io_buf* buf, 
    int flags, hasio::error_code &ec)
{
    msghdr msg = msghdr();
    msg.msg_iov = const_cast<io_buf*>(buf);
    msg.msg_iovlen = static_cast<int>(count);

    signed_size_type result = ::sendmsg(fd, &msg, flags);
    get_last_error(ec, result < 0);
    return result;
}

signed_size_type recv(socket_type fd, io_buf* buf, size_t count,
    int flags, hasio::error_code &ec)
{
    msghdr msg = msghdr();
    msg.msg_iov = buf;
    msg.msg_iovlen = static_cast<int>(count);
    signed_size_type result = ::recvmsg(fd, &msg, flags);
    get_last_error(ec, result < 0);
    return result;
}

socket_type socket(int af, int type, int protocol, 
    hasio::error_code &ec)
{
    return ::socket(af, type, protocol);
}

// network_to_host_longlong
// host_to_network_longlong

ulong_type network_to_host_long(ulong_type value)
{
  return ::ntohl(value);
}

ulong_type host_to_network_long(ulong_type value)
{
  return ::htonl(value);
}

ushort_type network_to_host_short(ushort_type value)
{
  return ::ntohs(value);
}

ushort_type host_to_network_short(ushort_type value)
{
    return ::htons(value);
}

}
}
}

#endif // HASIO_DETAIL_SOCKET_OPS_HPP